package edu.mercer.sse.tests.math;

import static org.junit.Assert.*;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.junit.Test;
import org.junit.After;

import com.google.common.collect.Lists;

import edu.mercer.sse.tasks.math.MonteCarloPi;
import edu.mercer.sse.tasks.math.PiAccuracyFunction;

/**
 * Test Monte Carlo Pi calculation for multi threaded environment.<p>
 *
 * Run multiple Monte Carlo Pi tasks concurrently in distinct threads. Each 
 * 	Task is a separate Callable object instance.<p>
 * 
 * Pi estimate should not consistently trend towards an increase in accuracy 
 * 	with successive tasks' executions.<p>
 * 
 * Ensure Pi results obtained are not using cumulative values.<p>
 * 
 * Each thread-global is distinct to each thread and not being used by other 
 * threads: identical sets of objects mapped to same names, yet global only to 
 * thread, not application.<p>
 */

public class TestMultiThread
{
	/** Number of tasks to create and execute */
	
	private static final Integer NUMBER_TASKS = 100; 
	
	/** Number of Monte Carlo guesses, attempts to run for each task */
	
	private static final Double MONTE_CARLO_GUESSES = 10000d; 
	
	/** Multiple thread environment: one thread per task */
	
	private final ExecutorService _threadEx = Executors
			.newFixedThreadPool(NUMBER_TASKS);
	
	/**
	 * Test Monte Carlo Pi estimation.<p>
	 * 
	 * Ensure accuracy does not substantially increase with successive 
	 * 	executions. Ensure cumulative <u>in</u>accuracy exceeds value derived 
	 * 	from more accurate TestSingleThread.<p>
	 * 
	 * @throws InterruptedException - thread interrupted
	 * 
	 * @throws ExecutionException - any NumberFormat, division or other errors 
	 * 	wrapped 
	 */
	
	@Test
	public void test() throws InterruptedException, ExecutionException
	{
		assertTrue( "no number of tasks", 
				
				NUMBER_TASKS > 0 );
		
		assertTrue( "no monte carlo number of guesses", 
				
				MONTE_CARLO_GUESSES > 0d );
		
		final List<MonteCarloPi> tasks = Lists.newArrayList();
		
		for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
		{
			tasks.add( new MonteCarloPi( "singlethreadtest", 
										 MONTE_CARLO_GUESSES ));
			
		}//for
		
		final List<Future<Double>> results = _threadEx.invokeAll( tasks );

		Boolean stillRunning = false;
		
		while( true )
		{
			for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
			{
				if( ! results.get(ct).isDone() )
				{
					stillRunning = true;
					break;
					
				}//if
			
			}//for
			
			if( ! stillRunning ) break;
			
			Thread.sleep(1000l);
			
		}//while
		
		ArrayList<Float> tasksAccuracy = Lists.newArrayList();
		
		Float tasksInaccuracyTotal = 0f;
		
		for( Integer ct=0; ct < NUMBER_TASKS; ct++ )
		{
			Double piEstimate = results.get(ct).get();
			
			DecimalFormat numFormatter = (DecimalFormat) DecimalFormat
					.getInstance();
			
			numFormatter.applyPattern ("#0.000000#");
			
			
			System.out.println( "Task "   
					
					+ ct
					
					+ " Accuracy " + numFormatter.format( 
							new PiAccuracyFunction().apply( piEstimate ) ) 
					
					+ " Pi " + numFormatter.format( piEstimate ));	
			
			tasksAccuracy.add( new Float( piEstimate ) );
			
			tasksInaccuracyTotal += ( 100f - new Float( 
					new PiAccuracyFunction().apply( piEstimate) ));
			
		}//for
		
		tasksInaccuracyTotal = tasksInaccuracyTotal / NUMBER_TASKS;
		
		System.out.println( "Sum of inaccuracy for " + NUMBER_TASKS 
				+ " : " 
				+ tasksInaccuracyTotal );
		
		assertTrue( "inaccuracy lower than expected", 
				
				tasksInaccuracyTotal > 0.1f );		
		
	}//method

	
	/**
	 * Shutdown executor service.<p>
	 */
	
	@After
	public void cleanUp()
	{
		_threadEx.shutdown();
		
	}//method

	
}//class
